//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"Classifier.h"#include"LookupCacheV4.h"#include"nsIPrefBranch.h"#include"nsIPrefService.h"#include"nsISimpleEnumerator.h"#include"nsIRandomGenerator.h"#include"nsIInputStream.h"#include"nsISeekableStream.h"#include"nsIFile.h"#include"nsNetCID.h"#include"nsPrintfCString.h"#include"nsThreadUtils.h"#include"mozilla/Telemetry.h"#include"mozilla/IntegerPrintfMacros.h"#include"mozilla/Logging.h"#include"mozilla/SyncRunnable.h"#include"mozilla/Base64.h"#include"mozilla/Unused.h"#include"mozilla/SizePrintfMacros.h"#include"mozilla/UniquePtr.h"#include"nsIUrlClassifierUtils.h"#include"nsUrlClassifierDBService.h"// MOZ_LOG=UrlClassifierDbService:5externmozilla::LazyLogModulegUrlClassifierDbServiceLog;#define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)#define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)#define STORE_DIRECTORY NS_LITERAL_CSTRING("safebrowsing")#define TO_DELETE_DIR_SUFFIX NS_LITERAL_CSTRING("-to_delete")#define BACKUP_DIR_SUFFIX NS_LITERAL_CSTRING("-backup")#define UPDATING_DIR_SUFFIX NS_LITERAL_CSTRING("-updating")#define METADATA_SUFFIX NS_LITERAL_CSTRING(".metadata")namespacemozilla{namespacesafebrowsing{namespace{// A scoped-clearer for nsTArray<TableUpdate*>.// The owning elements will be deleted and the array itself// will be cleared on exiting the scope.classScopedUpdatesClearer{public:explicitScopedUpdatesClearer(nsTArray<TableUpdate*>*aUpdates):mUpdatesArrayRef(aUpdates){for(autoupdate:*aUpdates){mUpdatesPointerHolder.AppendElement(update);}}~ScopedUpdatesClearer(){mUpdatesArrayRef->Clear();}private:nsTArray<TableUpdate*>*mUpdatesArrayRef;nsTArray<UniquePtr<TableUpdate>>mUpdatesPointerHolder;};}// End of unnamed namespace.voidClassifier::SplitTables(constnsACString&str,nsTArray<nsCString>&tables){tables.Clear();nsACString::const_iteratorbegin,iter,end;str.BeginReading(begin);str.EndReading(end);while(begin!=end){iter=begin;FindCharInReadable(',',iter,end);nsDependentCSubstringtable=Substring(begin,iter);if(!table.IsEmpty()){tables.AppendElement(Substring(begin,iter));}begin=iter;if(begin!=end){begin++;}}}nsresultClassifier::GetPrivateStoreDirectory(nsIFile*aRootStoreDirectory,constnsACString&aTableName,constnsACString&aProvider,nsIFile**aPrivateStoreDirectory){NS_ENSURE_ARG_POINTER(aPrivateStoreDirectory);if(!StringEndsWith(aTableName,NS_LITERAL_CSTRING("-proto"))){// Only V4 table names (ends with '-proto') would be stored// to per-provider sub-directory.nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);returnNS_OK;}if(aProvider.IsEmpty()){// When failing to get provider, just store in the root folder.nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);returnNS_OK;}nsCOMPtr<nsIFile>providerDirectory;// Clone first since we are gonna create a new directory.nsresultrv=aRootStoreDirectory->Clone(getter_AddRefs(providerDirectory));NS_ENSURE_SUCCESS(rv,rv);// Append the provider name to the root store directory.rv=providerDirectory->AppendNative(aProvider);NS_ENSURE_SUCCESS(rv,rv);// Ensure existence of the provider directory.booldirExists;rv=providerDirectory->Exists(&dirExists);NS_ENSURE_SUCCESS(rv,rv);if(!dirExists){LOG(("Creating private directory for %s",nsCString(aTableName).get()));rv=providerDirectory->Create(nsIFile::DIRECTORY_TYPE,0755);NS_ENSURE_SUCCESS(rv,rv);providerDirectory.forget(aPrivateStoreDirectory);returnrv;}// Store directory exists. Check if it's a directory.boolisDir;rv=providerDirectory->IsDirectory(&isDir);NS_ENSURE_SUCCESS(rv,rv);if(!isDir){returnNS_ERROR_FILE_DESTINATION_NOT_DIR;}providerDirectory.forget(aPrivateStoreDirectory);returnNS_OK;}Classifier::Classifier():mIsTableRequestResultOutdated(true),mUpdateInterrupted(true){NS_NewNamedThread(NS_LITERAL_CSTRING("Classifier Update"),getter_AddRefs(mUpdateThread));}Classifier::~Classifier(){Close();}nsresultClassifier::SetupPathNames(){// Get the root directory where to store all the databases.nsresultrv=mCacheDirectory->Clone(getter_AddRefs(mRootStoreDirectory));NS_ENSURE_SUCCESS(rv,rv);rv=mRootStoreDirectory->AppendNative(STORE_DIRECTORY);NS_ENSURE_SUCCESS(rv,rv);// Make sure LookupCaches (which are persistent and survive updates)// are reading/writing in the right place. We will be moving their// files "underneath" them during backup/restore.for(uint32_ti=0;i<mLookupCaches.Length();i++){mLookupCaches[i]->UpdateRootDirHandle(mRootStoreDirectory);}// Directory where to move a backup before an update.rv=mCacheDirectory->Clone(getter_AddRefs(mBackupDirectory));NS_ENSURE_SUCCESS(rv,rv);rv=mBackupDirectory->AppendNative(STORE_DIRECTORY+BACKUP_DIR_SUFFIX);NS_ENSURE_SUCCESS(rv,rv);// Directory where to be working on the update.rv=mCacheDirectory->Clone(getter_AddRefs(mUpdatingDirectory));NS_ENSURE_SUCCESS(rv,rv);rv=mUpdatingDirectory->AppendNative(STORE_DIRECTORY+UPDATING_DIR_SUFFIX);NS_ENSURE_SUCCESS(rv,rv);// Directory where to move the backup so we can atomically// delete (really move) it.rv=mCacheDirectory->Clone(getter_AddRefs(mToDeleteDirectory));NS_ENSURE_SUCCESS(rv,rv);rv=mToDeleteDirectory->AppendNative(STORE_DIRECTORY+TO_DELETE_DIR_SUFFIX);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultClassifier::CreateStoreDirectory(){// Ensure the safebrowsing directory exists.boolstoreExists;nsresultrv=mRootStoreDirectory->Exists(&storeExists);NS_ENSURE_SUCCESS(rv,rv);if(!storeExists){rv=mRootStoreDirectory->Create(nsIFile::DIRECTORY_TYPE,0755);NS_ENSURE_SUCCESS(rv,rv);}else{boolstoreIsDir;rv=mRootStoreDirectory->IsDirectory(&storeIsDir);NS_ENSURE_SUCCESS(rv,rv);if(!storeIsDir)returnNS_ERROR_FILE_DESTINATION_NOT_DIR;}returnNS_OK;}nsresultClassifier::Open(nsIFile&aCacheDirectory){// Remember the Local profile directory.nsresultrv=aCacheDirectory.Clone(getter_AddRefs(mCacheDirectory));NS_ENSURE_SUCCESS(rv,rv);// Create the handles to the update and backup directories.rv=SetupPathNames();NS_ENSURE_SUCCESS(rv,rv);// Clean up any to-delete directories that haven't been deleted yet.// This is still required for backward compatibility.rv=CleanToDelete();NS_ENSURE_SUCCESS(rv,rv);// If we met a crash during the previous update, "safebrowsing-updating"// directory will exist and let's remove it.rv=mUpdatingDirectory->Remove(true);if(NS_SUCCEEDED(rv)){// If the "safebrowsing-updating" exists, it implies a crash occurred// in the previous update.LOG(("We may have hit a crash in the previous update."));}// Check whether we have an incomplete update and recover from the// backup if so.rv=RecoverBackups();NS_ENSURE_SUCCESS(rv,rv);// Make sure the main store directory exists.rv=CreateStoreDirectory();NS_ENSURE_SUCCESS(rv,rv);mCryptoHash=do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID,&rv);NS_ENSURE_SUCCESS(rv,rv);// Build the list of know urlclassifier lists// XXX: Disk IO potentially on the main thread during startupRegenActiveTables();returnNS_OK;}voidClassifier::Close(){DropStores();}voidClassifier::Reset(){MOZ_ASSERT(NS_GetCurrentThread()!=mUpdateThread,"Reset() MUST NOT be called on update thread");LOG(("Reset() is called so we interrupt the update."));mUpdateInterrupted=true;autoresetFunc=[=]{DropStores();mRootStoreDirectory->Remove(true);mBackupDirectory->Remove(true);mUpdatingDirectory->Remove(true);mToDeleteDirectory->Remove(true);CreateStoreDirectory();RegenActiveTables();};if(!mUpdateThread){LOG(("Async update has been disabled. Just Reset() on worker thread."));resetFunc();return;}nsCOMPtr<nsIRunnable>r=NS_NewRunnableFunction("safebrowsing::Classifier::Reset",resetFunc);SyncRunnable::DispatchToThread(mUpdateThread,r);}voidClassifier::ResetTables(ClearTypeaType,constnsTArray<nsCString>&aTables){for(uint32_ti=0;i<aTables.Length();i++){LOG(("Resetting table: %s",aTables[i].get()));LookupCache*cache=GetLookupCache(aTables[i]);if(cache){// Remove any cached Completes for this table if clear type is Clear_Cacheif(aType==Clear_Cache){cache->ClearCache();}else{cache->ClearAll();}}}// Clear on-disk database if clear type is Clear_Allif(aType==Clear_All){DeleteTables(mRootStoreDirectory,aTables);RegenActiveTables();}}voidClassifier::DeleteTables(nsIFile*aDirectory,constnsTArray<nsCString>&aTables){nsCOMPtr<nsISimpleEnumerator>entries;nsresultrv=aDirectory->GetDirectoryEntries(getter_AddRefs(entries));NS_ENSURE_SUCCESS_VOID(rv);boolhasMore;while(NS_SUCCEEDED(rv=entries->HasMoreElements(&hasMore))&&hasMore){nsCOMPtr<nsISupports>supports;rv=entries->GetNext(getter_AddRefs(supports));NS_ENSURE_SUCCESS_VOID(rv);nsCOMPtr<nsIFile>file=do_QueryInterface(supports);NS_ENSURE_TRUE_VOID(file);// If |file| is a directory, recurse to find its entries as well.boolisDirectory;if(NS_FAILED(file->IsDirectory(&isDirectory))){continue;}if(isDirectory){DeleteTables(file,aTables);continue;}nsCStringleafName;rv=file->GetNativeLeafName(leafName);NS_ENSURE_SUCCESS_VOID(rv);leafName.Truncate(leafName.RFind("."));if(aTables.Contains(leafName)){if(NS_FAILED(file->Remove(false))){NS_WARNING(nsPrintfCString("Fail to remove file %s from the disk",leafName.get()).get());}}}NS_ENSURE_SUCCESS_VOID(rv);}voidClassifier::TableRequest(nsACString&aResult){MOZ_ASSERT(!NS_IsMainThread(),"TableRequest must be called on the classifier worker thread.");// This function and all disk I/O are guaranteed to occur// on the same thread so we don't need to add a lock around.if(!mIsTableRequestResultOutdated){aResult=mTableRequestResult;return;}// Generating v2 table info.nsTArray<nsCString>tables;ActiveTables(tables);for(uint32_ti=0;i<tables.Length();i++){HashStorestore(tables[i],GetProvider(tables[i]),mRootStoreDirectory);nsresultrv=store.Open();if(NS_FAILED(rv)){continue;}ChunkSet&adds=store.AddChunks();ChunkSet&subs=store.SubChunks();// Open HashStore will always succeed even that is not a v2 table.// So skip tables without add and sub chunks.if(adds.Length()==0&&subs.Length()==0){continue;}aResult.Append(store.TableName());aResult.Append(';');if(adds.Length()>0){aResult.AppendLiteral("a:");nsAutoCStringaddList;adds.Serialize(addList);aResult.Append(addList);}if(subs.Length()>0){if(adds.Length()>0)aResult.Append(':');aResult.AppendLiteral("s:");nsAutoCStringsubList;subs.Serialize(subList);aResult.Append(subList);}aResult.Append('\n');}// Load meta data from *.metadata files in the root directory.// Specifically for v4 tables.nsCStringmetadata;nsresultrv=LoadMetadata(mRootStoreDirectory,metadata);if(NS_SUCCEEDED(rv)){aResult.Append(metadata);}// Update the TableRequest result in-memory cache.mTableRequestResult=aResult;mIsTableRequestResultOutdated=false;}nsresultClassifier::Check(constnsACString&aSpec,constnsACString&aTables,LookupResultArray&aResults){Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_CHECK_TIME>timer;// Get the set of fragments based on the url. This is necessary because we// only look up at most 5 URLs per aSpec, even if aSpec has more than 5// components.nsTArray<nsCString>fragments;nsresultrv=LookupCache::GetLookupFragments(aSpec,&fragments);NS_ENSURE_SUCCESS(rv,rv);nsTArray<nsCString>activeTables;SplitTables(aTables,activeTables);nsTArray<LookupCache*>cacheArray;for(uint32_ti=0;i<activeTables.Length();i++){LOG(("Checking table %s",activeTables[i].get()));LookupCache*cache=GetLookupCache(activeTables[i]);if(cache){cacheArray.AppendElement(cache);}else{returnNS_ERROR_FAILURE;}}// Now check each lookup fragment against the entries in the DB.for(uint32_ti=0;i<fragments.Length();i++){CompletionlookupHash;lookupHash.FromPlaintext(fragments[i],mCryptoHash);if(LOG_ENABLED()){nsAutoCStringchecking;lookupHash.ToHexString(checking);LOG(("Checking fragment %s, hash %s (%X)",fragments[i].get(),checking.get(),lookupHash.ToUint32()));}for(uint32_ti=0;i<cacheArray.Length();i++){LookupCache*cache=cacheArray[i];boolhas,confirmed;uint32_tmatchLength;rv=cache->Has(lookupHash,&has,&matchLength,&confirmed);NS_ENSURE_SUCCESS(rv,rv);if(has){LookupResult*result=aResults.AppendElement();if(!result)returnNS_ERROR_OUT_OF_MEMORY;LOG(("Found a result in %s: %s",cache->TableName().get(),confirmed?"confirmed.":"Not confirmed."));result->hash.complete=lookupHash;result->mConfirmed=confirmed;result->mTableName.Assign(cache->TableName());result->mPartialHashLength=confirmed?COMPLETE_SIZE:matchLength;result->mProtocolV2=LookupCache::Cast<LookupCacheV2>(cache);}}}returnNS_OK;}staticnsresultSwapDirectoryContent(nsIFile*aDir1,nsIFile*aDir2,nsIFile*aParentDir,nsIFile*aTempDir){// Pre-condition: |aDir1| and |aDir2| are directory and their parent// are both |aParentDir|.//// Post-condition: The locations where aDir1 and aDir2 point to will not// change but their contents will be exchanged. If we failed// to swap their content, everything will be rolled back.nsAutoCStringtempDirName;aTempDir->GetNativeLeafName(tempDirName);nsresultrv;nsAutoCStringdirName1,dirName2;aDir1->GetNativeLeafName(dirName1);aDir2->GetNativeLeafName(dirName2);LOG(("Swapping directories %s and %s...",dirName1.get(),dirName2.get()));// 1. Rename "dirName1" to "temp"rv=aDir1->RenameToNative(nullptr,tempDirName);if(NS_FAILED(rv)){LOG(("Unable to rename %s to %s",dirName1.get(),tempDirName.get()));returnrv;// Nothing to roll back.}// 1.1. Create a handle for temp directory. This is required since// |nsIFile.rename| will not change the location where the// object points to.nsCOMPtr<nsIFile>tempDirectory;rv=aParentDir->Clone(getter_AddRefs(tempDirectory));rv=tempDirectory->AppendNative(tempDirName);// 2. Rename "dirName2" to "dirName1".rv=aDir2->RenameToNative(nullptr,dirName1);if(NS_FAILED(rv)){LOG(("Failed to rename %s to %s. Rename temp directory back to %s",dirName2.get(),dirName1.get(),dirName1.get()));nsresultrbrv=tempDirectory->RenameToNative(nullptr,dirName1);NS_ENSURE_SUCCESS(rbrv,rbrv);returnrv;}// 3. Rename "temp" to "dirName2".rv=tempDirectory->RenameToNative(nullptr,dirName2);if(NS_FAILED(rv)){LOG(("Failed to rename temp directory to %s. ",dirName2.get()));// We've done (1) renaming "dir1 to temp" and// (2) renaming "dir2 to dir1"// so the rollback is// (1) renaming "dir1 to dir2" and// (2) renaming "temp to dir1"nsresultrbrv;// rollback resultrbrv=aDir1->RenameToNative(nullptr,dirName2);NS_ENSURE_SUCCESS(rbrv,rbrv);rbrv=tempDirectory->RenameToNative(nullptr,dirName1);NS_ENSURE_SUCCESS(rbrv,rbrv);returnrv;}returnrv;}voidClassifier::RemoveUpdateIntermediaries(){// Remove old LookupCaches.for(autoc:mNewLookupCaches){deletec;}mNewLookupCaches.Clear();// Remove the "old" directory. (despite its looking-new name)if(NS_FAILED(mUpdatingDirectory->Remove(true))){// If the directory is locked from removal for some reason,// we will fail here and it doesn't matter until the next// update. (the next udpate will fail due to the removable// "safebrowsing-udpating" directory.)LOG(("Failed to remove updating directory."));}}voidClassifier::CopyAndInvalidateFullHashCache(){MOZ_ASSERT(NS_GetCurrentThread()!=mUpdateThread,"CopyAndInvalidateFullHashCache cannot be called on update thread ""since it mutates mLookupCaches which is only safe on ""worker thread.");// New lookup caches are built from disk, data likes cache which is// generated online won't exist. We have to manually copy cache from// old LookupCache to new LookupCache.for(auto&newCache:mNewLookupCaches){for(auto&oldCache:mLookupCaches){if(oldCache->TableName()==newCache->TableName()){newCache->CopyFullHashCache(oldCache);break;}}}// Clear cache when update.// Invalidate cache entries in CopyAndInvalidateFullHashCache because only// at this point we will have cache data in LookupCache.for(auto&newCache:mNewLookupCaches){newCache->InvalidateExpiredCacheEntries();}}voidClassifier::MergeNewLookupCaches(){MOZ_ASSERT(NS_GetCurrentThread()!=mUpdateThread,"MergeNewLookupCaches cannot be called on update thread ""since it mutates mLookupCaches which is only safe on ""worker thread.");for(auto&newCache:mNewLookupCaches){// For each element in mNewLookCaches, it will be swapped with// - An old cache in mLookupCache with the same table name or// - nullptr (mLookupCache will be expaned) otherwise.size_tswapIndex=0;for(;swapIndex<mLookupCaches.Length();swapIndex++){if(mLookupCaches[swapIndex]->TableName()==newCache->TableName()){break;}}if(swapIndex==mLookupCaches.Length()){mLookupCaches.AppendElement(nullptr);}Swap(mLookupCaches[swapIndex],newCache);mLookupCaches[swapIndex]->UpdateRootDirHandle(mRootStoreDirectory);}// At this point, mNewLookupCaches's length remains the same but// will contain either old cache (override) or nullptr (append).}nsresultClassifier::SwapInNewTablesAndCleanup(){nsresultrv;// Step 1. Swap in on-disk tables. The idea of using "safebrowsing-backup"// as the intermediary directory is we can get databases recovered if// crash occurred in any step of the swap. (We will recover from// "safebrowsing-backup" in OpenDb().)rv=SwapDirectoryContent(mUpdatingDirectory,// contains new tablesmRootStoreDirectory,// contains old tablesmCacheDirectory,// common parent dirmBackupDirectory);// intermediary dir for swapif(NS_FAILED(rv)){LOG(("Failed to swap in on-disk tables."));RemoveUpdateIntermediaries();returnrv;}// Step 2. Merge mNewLookupCaches into mLookupCaches. The outdated// LookupCaches will be stored in mNewLookupCaches and be cleaned// up later.MergeNewLookupCaches();// Step 3. Re-generate active tables based on on-disk tables.rv=RegenActiveTables();if(NS_FAILED(rv)){LOG(("Failed to re-generate active tables!"));}// Step 4. Clean up intermediaries for update.RemoveUpdateIntermediaries();// Step 5. Invalidate cached tableRequest request.mIsTableRequestResultOutdated=true;LOG(("Done swap in updated tables."));returnrv;}voidClassifier::FlushAndDisableAsyncUpdate(){LOG(("Classifier::FlushAndDisableAsyncUpdate [%p, %p]",this,mUpdateThread.get()));if(!mUpdateThread){LOG(("Async update has been disabled."));return;}mUpdateThread->Shutdown();mUpdateThread=nullptr;}nsresultClassifier::AsyncApplyUpdates(nsTArray<TableUpdate*>*aUpdates,constAsyncUpdateCallback&aCallback){LOG(("Classifier::AsyncApplyUpdates"));if(!mUpdateThread){LOG(("Async update has already been disabled."));returnNS_ERROR_FAILURE;}// Caller thread | Update thread// --------------------------------------------------------// | ApplyUpdatesBackground// (processing other task) | (bg-update done. ping back to caller thread)// (processing other task) | idle...// ApplyUpdatesForeground |// callback |mUpdateInterrupted=false;nsresultrv=mRootStoreDirectory->Clone(getter_AddRefs(mRootStoreDirectoryForUpdate));if(NS_FAILED(rv)){LOG(("Failed to clone mRootStoreDirectory for update."));returnrv;}nsCOMPtr<nsIThread>callerThread=NS_GetCurrentThread();MOZ_ASSERT(callerThread!=mUpdateThread);nsCOMPtr<nsIRunnable>bgRunnable=NS_NewRunnableFunction("safebrowsing::Classifier::AsyncApplyUpdates",[=]{MOZ_ASSERT(NS_GetCurrentThread()==mUpdateThread,"MUST be on update thread");LOG(("Step 1. ApplyUpdatesBackground on update thread."));nsCStringfailedTableName;nsresultbgRv=ApplyUpdatesBackground(aUpdates,failedTableName);nsCOMPtr<nsIRunnable>fgRunnable=NS_NewRunnableFunction("safebrowsing::Classifier::AsyncApplyUpdates",[=]{MOZ_ASSERT(NS_GetCurrentThread()==callerThread,"MUST be on caller thread");LOG(("Step 2. ApplyUpdatesForeground on caller thread"));nsresultrv=ApplyUpdatesForeground(bgRv,failedTableName);;LOG(("Step 3. Updates applied! Fire callback."));aCallback(rv);});callerThread->Dispatch(fgRunnable,NS_DISPATCH_NORMAL);});returnmUpdateThread->Dispatch(bgRunnable,NS_DISPATCH_NORMAL);}nsresultClassifier::ApplyUpdatesBackground(nsTArray<TableUpdate*>*aUpdates,nsACString&aFailedTableName){// |mUpdateInterrupted| is guaranteed to have been unset.// If |mUpdateInterrupted| is set at any point, Reset() must have// been called then we need to interrupt the update process.// We only add checkpoints for non-trivial tasks.if(!aUpdates||aUpdates->Length()==0){returnNS_OK;}nsCOMPtr<nsIUrlClassifierUtils>urlUtil=do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);nsCStringprovider;// Assume all TableUpdate objects should have the same provider.urlUtil->GetTelemetryProvider((*aUpdates)[0]->TableName(),provider);Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_KEYED_UPDATE_TIME>keyedTimer(provider);PRIntervalTimeclockStart=0;if(LOG_ENABLED()){clockStart=PR_IntervalNow();}nsresultrv;{ScopedUpdatesClearerscopedUpdatesClearer(aUpdates);{// Check point 1: Copying file takes time so we check here.if(mUpdateInterrupted){LOG(("Update is interrupted. Don't copy files."));returnNS_OK;}rv=CopyInUseDirForUpdate();// i.e. mUpdatingDirectory will be setup.if(NS_FAILED(rv)){LOG(("Failed to copy in-use directory for update."));returnrv;}}LOG(("Applying %"PRIuSIZE" table updates.",aUpdates->Length()));for(uint32_ti=0;i<aUpdates->Length();i++){// Previous UpdateHashStore() may have consumed this update..if((*aUpdates)[i]){// Run all updates for one tablensCStringupdateTable(aUpdates->ElementAt(i)->TableName());// Check point 2: Processing downloaded data takes time.if(mUpdateInterrupted){LOG(("Update is interrupted. Stop building new tables."));returnNS_OK;}// Will update the mirrored in-memory and on-disk databases.if(TableUpdate::Cast<TableUpdateV2>((*aUpdates)[i])){rv=UpdateHashStore(aUpdates,updateTable);}else{rv=UpdateTableV4(aUpdates,updateTable);}if(NS_FAILED(rv)){aFailedTableName=updateTable;RemoveUpdateIntermediaries();returnrv;}}}}// End of scopedUpdatesClearer scope.if(LOG_ENABLED()){PRIntervalTimeclockEnd=PR_IntervalNow();LOG(("update took %dms\n",PR_IntervalToMilliseconds(clockEnd-clockStart)));}returnrv;}nsresultClassifier::ApplyUpdatesForeground(nsresultaBackgroundRv,constnsACString&aFailedTableName){if(mUpdateInterrupted){LOG(("Update is interrupted! Just remove update intermediaries."));RemoveUpdateIntermediaries();returnNS_OK;}if(NS_SUCCEEDED(aBackgroundRv)){// Copy and Invalidate fullhash cache here because this call requires// mLookupCaches which is only available on work-threadCopyAndInvalidateFullHashCache();returnSwapInNewTablesAndCleanup();}if(NS_ERROR_OUT_OF_MEMORY!=aBackgroundRv){ResetTables(Clear_All,nsTArray<nsCString>{nsCString(aFailedTableName)});}returnaBackgroundRv;}nsresultClassifier::ApplyFullHashes(nsTArray<TableUpdate*>*aUpdates){LOG(("Applying %"PRIuSIZE" table gethashes.",aUpdates->Length()));ScopedUpdatesClearerscopedUpdatesClearer(aUpdates);for(uint32_ti=0;i<aUpdates->Length();i++){TableUpdate*update=aUpdates->ElementAt(i);nsresultrv=UpdateCache(update);NS_ENSURE_SUCCESS(rv,rv);aUpdates->ElementAt(i)=nullptr;}returnNS_OK;}voidClassifier::GetCacheInfo(constnsACString&aTable,nsIUrlClassifierCacheInfo**aCache){LookupCache*lookupCache=GetLookupCache(aTable);if(!lookupCache){return;}lookupCache->GetCacheInfo(aCache);}voidClassifier::DropStores(){for(uint32_ti=0;i<mLookupCaches.Length();i++){deletemLookupCaches[i];}mLookupCaches.Clear();}nsresultClassifier::RegenActiveTables(){mActiveTablesCache.Clear();nsTArray<nsCString>foundTables;ScanStoreDir(mRootStoreDirectory,foundTables);for(uint32_ti=0;i<foundTables.Length();i++){nsCStringtable(foundTables[i]);LookupCache*lookupCache=GetLookupCache(table);if(!lookupCache){continue;}if(!lookupCache->IsPrimed()){continue;}if(LookupCache::Cast<LookupCacheV4>(lookupCache)){LOG(("Active v4 table: %s",table.get()));}else{HashStorestore(table,GetProvider(table),mRootStoreDirectory);nsresultrv=store.Open();if(NS_FAILED(rv)){continue;}constChunkSet&adds=store.AddChunks();constChunkSet&subs=store.SubChunks();if(adds.Length()==0&&subs.Length()==0){continue;}LOG(("Active v2 table: %s",store.TableName().get()));}mActiveTablesCache.AppendElement(table);}returnNS_OK;}nsresultClassifier::ScanStoreDir(nsIFile*aDirectory,nsTArray<nsCString>&aTables){nsCOMPtr<nsISimpleEnumerator>entries;nsresultrv=aDirectory->GetDirectoryEntries(getter_AddRefs(entries));NS_ENSURE_SUCCESS(rv,rv);boolhasMore;while(NS_SUCCEEDED(rv=entries->HasMoreElements(&hasMore))&&hasMore){nsCOMPtr<nsISupports>supports;rv=entries->GetNext(getter_AddRefs(supports));NS_ENSURE_SUCCESS(rv,rv);nsCOMPtr<nsIFile>file=do_QueryInterface(supports);// If |file| is a directory, recurse to find its entries as well.boolisDirectory;if(NS_FAILED(file->IsDirectory(&isDirectory))){continue;}if(isDirectory){ScanStoreDir(file,aTables);continue;}nsCStringleafName;rv=file->GetNativeLeafName(leafName);NS_ENSURE_SUCCESS(rv,rv);// Both v2 and v4 contain .pset filensCStringsuffix(NS_LITERAL_CSTRING(".pset"));int32_tdot=leafName.RFind(suffix,0);if(dot!=-1){leafName.Cut(dot,suffix.Length());aTables.AppendElement(leafName);}}NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultClassifier::ActiveTables(nsTArray<nsCString>&aTables){aTables=mActiveTablesCache;returnNS_OK;}nsresultClassifier::CleanToDelete(){boolexists;nsresultrv=mToDeleteDirectory->Exists(&exists);NS_ENSURE_SUCCESS(rv,rv);if(exists){rv=mToDeleteDirectory->Remove(true);NS_ENSURE_SUCCESS(rv,rv);}returnNS_OK;}#ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATESalready_AddRefed<nsIFile>Classifier::GetFailedUpdateDirectroy(){nsCStringfailedUpdatekDirName=STORE_DIRECTORY+nsCString("-failedupdate");nsCOMPtr<nsIFile>failedUpdatekDirectory;if(NS_FAILED(mCacheDirectory->Clone(getter_AddRefs(failedUpdatekDirectory)))||NS_FAILED(failedUpdatekDirectory->AppendNative(failedUpdatekDirName))){LOG(("Failed to init failedUpdatekDirectory."));returnnullptr;}returnfailedUpdatekDirectory.forget();}nsresultClassifier::DumpRawTableUpdates(constnsACString&aRawUpdates){LOG(("Dumping raw table updates..."));DumpFailedUpdate();nsCOMPtr<nsIFile>failedUpdatekDirectory=GetFailedUpdateDirectroy();// Create tableupdate.bin and dump raw table update data.nsCOMPtr<nsIFile>rawTableUpdatesFile;nsCOMPtr<nsIOutputStream>outputStream;if(NS_FAILED(failedUpdatekDirectory->Clone(getter_AddRefs(rawTableUpdatesFile)))||NS_FAILED(rawTableUpdatesFile->AppendNative(nsCString("tableupdates.bin")))||NS_FAILED(NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),rawTableUpdatesFile,PR_WRONLY|PR_TRUNCATE|PR_CREATE_FILE))){LOG(("Failed to create file to dump raw table updates."));returnNS_ERROR_FAILURE;}// Write out the data.uint32_twritten;nsresultrv=outputStream->Write(aRawUpdates.BeginReading(),aRawUpdates.Length(),&written);NS_ENSURE_SUCCESS(rv,rv);NS_ENSURE_TRUE(written==aRawUpdates.Length(),NS_ERROR_FAILURE);returnrv;}nsresultClassifier::DumpFailedUpdate(){LOG(("Dumping failed update..."));nsCOMPtr<nsIFile>failedUpdatekDirectory=GetFailedUpdateDirectroy();// Remove the "failed update" directory no matter it exists or not.// Failure is fine because the directory may not exist.failedUpdatekDirectory->Remove(true);nsCStringfailedUpdatekDirName;nsresultrv=failedUpdatekDirectory->GetNativeLeafName(failedUpdatekDirName);NS_ENSURE_SUCCESS(rv,rv);// Copy the in-use directory to a clean "failed update" directory.nsCOMPtr<nsIFile>inUseDirectory;if(NS_FAILED(mRootStoreDirectory->Clone(getter_AddRefs(inUseDirectory)))||NS_FAILED(inUseDirectory->CopyToNative(nullptr,failedUpdatekDirName))){LOG(("Failed to move in-use to the \"failed update\" directory %s",failedUpdatekDirName.get()));returnNS_ERROR_FAILURE;}returnrv;}#endif // MOZ_SAFEBROWSING_DUMP_FAILED_UPDATESnsresultClassifier::CopyInUseDirForUpdate(){LOG(("Copy in-use directory content for update."));// We copy everything from in-use directory to a temporary directory// for updating.nsCStringupdatingDirName;nsresultrv=mUpdatingDirectory->GetNativeLeafName(updatingDirName);NS_ENSURE_SUCCESS(rv,rv);// Remove the destination directory first (just in case) the do the copy.mUpdatingDirectory->Remove(true);if(!mRootStoreDirectoryForUpdate){LOG(("mRootStoreDirectoryForUpdate is null."));returnNS_ERROR_NULL_POINTER;}rv=mRootStoreDirectoryForUpdate->CopyToNative(nullptr,updatingDirName);NS_ENSURE_SUCCESS(rv,rv);returnNS_OK;}nsresultClassifier::RecoverBackups(){boolbackupExists;nsresultrv=mBackupDirectory->Exists(&backupExists);NS_ENSURE_SUCCESS(rv,rv);if(backupExists){// Remove the safebrowsing dir if it existsnsCStringstoreDirName;rv=mRootStoreDirectory->GetNativeLeafName(storeDirName);NS_ENSURE_SUCCESS(rv,rv);boolstoreExists;rv=mRootStoreDirectory->Exists(&storeExists);NS_ENSURE_SUCCESS(rv,rv);if(storeExists){rv=mRootStoreDirectory->Remove(true);NS_ENSURE_SUCCESS(rv,rv);}// Move the backup to the store locationrv=mBackupDirectory->MoveToNative(nullptr,storeDirName);NS_ENSURE_SUCCESS(rv,rv);// mBackupDirectory now points to storeDir, fix up.rv=SetupPathNames();NS_ENSURE_SUCCESS(rv,rv);}returnNS_OK;}boolClassifier::CheckValidUpdate(nsTArray<TableUpdate*>*aUpdates,constnsACString&aTable){// take the quick exit if there is no valid update for us// (common case)uint32_tvalidupdates=0;for(uint32_ti=0;i<aUpdates->Length();i++){TableUpdate*update=aUpdates->ElementAt(i);if(!update||!update->TableName().Equals(aTable))continue;if(update->Empty()){aUpdates->ElementAt(i)=nullptr;continue;}validupdates++;}if(!validupdates){// This can happen if the update was only valid for one table.returnfalse;}returntrue;}nsCStringClassifier::GetProvider(constnsACString&aTableName){nsCOMPtr<nsIUrlClassifierUtils>urlUtil=do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);nsCStringprovider;nsresultrv=urlUtil->GetProvider(aTableName,provider);returnNS_SUCCEEDED(rv)?provider:EmptyCString();}/* * This will consume+delete updates from the passed nsTArray.*/nsresultClassifier::UpdateHashStore(nsTArray<TableUpdate*>*aUpdates,constnsACString&aTable){if(nsUrlClassifierDBService::ShutdownHasStarted()){returnNS_ERROR_UC_UPDATE_SHUTDOWNING;}LOG(("Classifier::UpdateHashStore(%s)",PromiseFlatCString(aTable).get()));HashStorestore(aTable,GetProvider(aTable),mUpdatingDirectory);if(!CheckValidUpdate(aUpdates,store.TableName())){returnNS_OK;}nsresultrv=store.Open();NS_ENSURE_SUCCESS(rv,rv);rv=store.BeginUpdate();NS_ENSURE_SUCCESS(rv,rv);// Read the part of the store that is (only) in the cacheLookupCacheV2*lookupCache=LookupCache::Cast<LookupCacheV2>(GetLookupCacheForUpdate(store.TableName()));if(!lookupCache){returnNS_ERROR_UC_UPDATE_TABLE_NOT_FOUND;}FallibleTArray<uint32_t>AddPrefixHashes;rv=lookupCache->GetPrefixes(AddPrefixHashes);NS_ENSURE_SUCCESS(rv,rv);rv=store.AugmentAdds(AddPrefixHashes);NS_ENSURE_SUCCESS(rv,rv);AddPrefixHashes.Clear();uint32_tapplied=0;for(uint32_ti=0;i<aUpdates->Length();i++){TableUpdate*update=aUpdates->ElementAt(i);if(!update||!update->TableName().Equals(store.TableName()))continue;rv=store.ApplyUpdate(*update);NS_ENSURE_SUCCESS(rv,rv);applied++;autoupdateV2=TableUpdate::Cast<TableUpdateV2>(update);if(updateV2){LOG(("Applied update to table %s:",store.TableName().get()));LOG((" %d add chunks",updateV2->AddChunks().Length()));LOG((" %"PRIuSIZE" add prefixes",updateV2->AddPrefixes().Length()));LOG((" %"PRIuSIZE" add completions",updateV2->AddCompletes().Length()));LOG((" %d sub chunks",updateV2->SubChunks().Length()));LOG((" %"PRIuSIZE" sub prefixes",updateV2->SubPrefixes().Length()));LOG((" %"PRIuSIZE" sub completions",updateV2->SubCompletes().Length()));LOG((" %d add expirations",updateV2->AddExpirations().Length()));LOG((" %d sub expirations",updateV2->SubExpirations().Length()));}aUpdates->ElementAt(i)=nullptr;}LOG(("Applied %d update(s) to %s.",applied,store.TableName().get()));rv=store.Rebuild();NS_ENSURE_SUCCESS(rv,rv);LOG(("Table %s now has:",store.TableName().get()));LOG((" %d add chunks",store.AddChunks().Length()));LOG((" %"PRIuSIZE" add prefixes",store.AddPrefixes().Length()));LOG((" %"PRIuSIZE" add completions",store.AddCompletes().Length()));LOG((" %d sub chunks",store.SubChunks().Length()));LOG((" %"PRIuSIZE" sub prefixes",store.SubPrefixes().Length()));LOG((" %"PRIuSIZE" sub completions",store.SubCompletes().Length()));rv=store.WriteFile();NS_ENSURE_SUCCESS(rv,rv);// At this point the store is updated and written out to disk, but// the data is still in memory. Build our quick-lookup table here.rv=lookupCache->Build(store.AddPrefixes(),store.AddCompletes());NS_ENSURE_SUCCESS(rv,NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE);#if defined(DEBUG)lookupCache->DumpCompletions();#endifrv=lookupCache->WriteFile();NS_ENSURE_SUCCESS(rv,NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK);LOG(("Successfully updated %s",store.TableName().get()));returnNS_OK;}nsresultClassifier::UpdateTableV4(nsTArray<TableUpdate*>*aUpdates,constnsACString&aTable){MOZ_ASSERT(!NS_IsMainThread(),"UpdateTableV4 must be called on the classifier worker thread.");if(nsUrlClassifierDBService::ShutdownHasStarted()){returnNS_ERROR_UC_UPDATE_SHUTDOWNING;}LOG(("Classifier::UpdateTableV4(%s)",PromiseFlatCString(aTable).get()));if(!CheckValidUpdate(aUpdates,aTable)){returnNS_OK;}LookupCacheV4*lookupCache=LookupCache::Cast<LookupCacheV4>(GetLookupCacheForUpdate(aTable));if(!lookupCache){returnNS_ERROR_UC_UPDATE_TABLE_NOT_FOUND;}nsresultrv=NS_OK;// If there are multiple updates for the same table, prefixes1 & prefixes2// will act as input and output in turn to reduce memory copy overhead.PrefixStringMapprefixes1,prefixes2;PrefixStringMap*input=&prefixes1;PrefixStringMap*output=&prefixes2;TableUpdateV4*lastAppliedUpdate=nullptr;for(uint32_ti=0;i<aUpdates->Length();i++){TableUpdate*update=aUpdates->ElementAt(i);if(!update||!update->TableName().Equals(aTable)){continue;}autoupdateV4=TableUpdate::Cast<TableUpdateV4>(update);NS_ENSURE_TRUE(updateV4,NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND);if(updateV4->IsFullUpdate()){input->Clear();output->Clear();rv=lookupCache->ApplyUpdate(updateV4,*input,*output);if(NS_FAILED(rv)){returnrv;}}else{// If both prefix sets are empty, this means we are doing a partial update// without a prior full/partial update in the loop. In this case we should// get prefixes from the lookup cache first.if(prefixes1.IsEmpty()&&prefixes2.IsEmpty()){lookupCache->GetPrefixes(prefixes1);}else{MOZ_ASSERT(prefixes1.IsEmpty()^prefixes2.IsEmpty());// When there are multiple partial updates, input should always point// to the non-empty prefix set(filled by previous full/partial update).// output should always point to the empty prefix set.input=prefixes1.IsEmpty()?&prefixes2:&prefixes1;output=prefixes1.IsEmpty()?&prefixes1:&prefixes2;}rv=lookupCache->ApplyUpdate(updateV4,*input,*output);if(NS_FAILED(rv)){returnrv;}input->Clear();}// Keep track of the last applied update.lastAppliedUpdate=updateV4;aUpdates->ElementAt(i)=nullptr;}rv=lookupCache->Build(*output);NS_ENSURE_SUCCESS(rv,NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE);rv=lookupCache->WriteFile();NS_ENSURE_SUCCESS(rv,NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK);if(lastAppliedUpdate){LOG(("Write meta data of the last applied update."));rv=lookupCache->WriteMetadata(lastAppliedUpdate);NS_ENSURE_SUCCESS(rv,NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK);}LOG(("Successfully updated %s\n",PromiseFlatCString(aTable).get()));returnNS_OK;}nsresultClassifier::UpdateCache(TableUpdate*aUpdate){if(!aUpdate){returnNS_OK;}nsAutoCStringtable(aUpdate->TableName());LOG(("Classifier::UpdateCache(%s)",table.get()));LookupCache*lookupCache=GetLookupCache(table);if(!lookupCache){returnNS_ERROR_FAILURE;}autolookupV2=LookupCache::Cast<LookupCacheV2>(lookupCache);if(lookupV2){autoupdateV2=TableUpdate::Cast<TableUpdateV2>(aUpdate);lookupV2->AddGethashResultToCache(updateV2->AddCompletes(),updateV2->MissPrefixes());}else{autolookupV4=LookupCache::Cast<LookupCacheV4>(lookupCache);if(!lookupV4){returnNS_ERROR_FAILURE;}autoupdateV4=TableUpdate::Cast<TableUpdateV4>(aUpdate);lookupV4->AddFullHashResponseToCache(updateV4->FullHashResponse());}#if defined(DEBUG)lookupCache->DumpCache();#endifreturnNS_OK;}LookupCache*Classifier::GetLookupCache(constnsACString&aTable,boolaForUpdate){if(aForUpdate){MOZ_ASSERT(NS_GetCurrentThread()==mUpdateThread,"GetLookupCache(aForUpdate==true) can only be called on update thread.");}nsTArray<LookupCache*>&lookupCaches=aForUpdate?mNewLookupCaches:mLookupCaches;auto&rootStoreDirectory=aForUpdate?mUpdatingDirectory:mRootStoreDirectory;for(autoc:lookupCaches){if(c->TableName().Equals(aTable)){returnc;}}// TODO : Bug 1302600, It would be better if we have a more general non-main// thread method to convert table name to protocol version. Currently// we can only know this by checking if the table name ends with '-proto'.UniquePtr<LookupCache>cache;nsCStringprovider=GetProvider(aTable);if(StringEndsWith(aTable,NS_LITERAL_CSTRING("-proto"))){cache=MakeUnique<LookupCacheV4>(aTable,provider,rootStoreDirectory);}else{cache=MakeUnique<LookupCacheV2>(aTable,provider,rootStoreDirectory);}nsresultrv=cache->Init();if(NS_FAILED(rv)){returnnullptr;}rv=cache->Open();if(NS_SUCCEEDED(rv)){lookupCaches.AppendElement(cache.get());returncache.release();}// At this point we failed to open LookupCache.//// GetLookupCache for update and for other usage will run on update thread// and worker thread respectively (Bug 1339760). Removing stuff only in// their own realms potentially increases the concurrency.if(aForUpdate){// Remove intermediaries no matter if it's due to file corruption or not.RemoveUpdateIntermediaries();returnnullptr;}// Non-update case.if(rv==NS_ERROR_FILE_CORRUPTED){Reset();// Not including the update intermediaries.}returnnullptr;}nsresultClassifier::ReadNoiseEntries(constPrefix&aPrefix,constnsACString&aTableName,uint32_taCount,PrefixArray*aNoiseEntries){FallibleTArray<uint32_t>prefixes;nsresultrv;LookupCache*cache=GetLookupCache(aTableName);if(!cache){returnNS_ERROR_FAILURE;}LookupCacheV2*cacheV2=LookupCache::Cast<LookupCacheV2>(cache);if(cacheV2){rv=cacheV2->GetPrefixes(prefixes);}else{rv=LookupCache::Cast<LookupCacheV4>(cache)->GetFixedLengthPrefixes(prefixes);}NS_ENSURE_SUCCESS(rv,rv);if(prefixes.Length()==0){NS_WARNING("Could not find prefix in PrefixSet during noise lookup");returnNS_ERROR_FAILURE;}// We do not want to simply pick random prefixes, because this would allow// averaging out the noise by analysing the traffic from Firefox users.// Instead, we ensure the 'noise' is the same for the same prefix by seeding// the random number generator with the prefix. We prefer not to use rand()// which isn't thread safe, and the reseeding of which could trip up other// parts othe code that expect actual random numbers.// Here we use a simple LCG (Linear Congruential Generator) to generate// random numbers. We seed the LCG with the prefix we are generating noise// for.// http://en.wikipedia.org/wiki/Linear_congruential_generatoruint32_tm=prefixes.Length();uint32_ta=aCount%m;uint32_tidx=aPrefix.ToUint32()%m;for(size_ti=0;i<aCount;i++){idx=(a*idx+a)%m;PrefixnewPrefix;uint32_thash=prefixes[idx];// In the case V4 little endian, we did swapping endian when converting from char* to// int, should revert endian to make sure we will send hex string correctly// See https://bugzilla.mozilla.org/show_bug.cgi?id=1283007#c23if(!cacheV2&&!bool(MOZ_BIG_ENDIAN)){hash=NativeEndian::swapFromBigEndian(prefixes[idx]);}newPrefix.FromUint32(hash);if(newPrefix!=aPrefix){aNoiseEntries->AppendElement(newPrefix);}}returnNS_OK;}nsresultClassifier::LoadMetadata(nsIFile*aDirectory,nsACString&aResult){nsCOMPtr<nsISimpleEnumerator>entries;nsresultrv=aDirectory->GetDirectoryEntries(getter_AddRefs(entries));NS_ENSURE_SUCCESS(rv,rv);NS_ENSURE_ARG_POINTER(entries);boolhasMore;while(NS_SUCCEEDED(rv=entries->HasMoreElements(&hasMore))&&hasMore){nsCOMPtr<nsISupports>supports;rv=entries->GetNext(getter_AddRefs(supports));NS_ENSURE_SUCCESS(rv,rv);nsCOMPtr<nsIFile>file=do_QueryInterface(supports);// If |file| is a directory, recurse to find its entries as well.boolisDirectory;if(NS_FAILED(file->IsDirectory(&isDirectory))){continue;}if(isDirectory){LoadMetadata(file,aResult);continue;}// Truncate file extension to get the table name.nsCStringtableName;rv=file->GetNativeLeafName(tableName);NS_ENSURE_SUCCESS(rv,rv);int32_tdot=tableName.RFind(METADATA_SUFFIX,0);if(dot==-1){continue;}tableName.Cut(dot,METADATA_SUFFIX.Length());LookupCacheV4*lookupCache=LookupCache::Cast<LookupCacheV4>(GetLookupCache(tableName));if(!lookupCache){continue;}nsCStringstate;nsCStringchecksum;rv=lookupCache->LoadMetadata(state,checksum);if(NS_FAILED(rv)){LOG(("Failed to get metadata for table %s",tableName.get()));continue;}// The state might include '\n' so that we have to encode.nsAutoCStringstateBase64;rv=Base64Encode(state,stateBase64);NS_ENSURE_SUCCESS(rv,rv);nsAutoCStringchecksumBase64;rv=Base64Encode(checksum,checksumBase64);NS_ENSURE_SUCCESS(rv,rv);LOG(("Appending state '%s' and checksum '%s' for table %s",stateBase64.get(),checksumBase64.get(),tableName.get()));aResult.AppendPrintf("%s;%s:%s\n",tableName.get(),stateBase64.get(),checksumBase64.get());}returnrv;}}// namespace safebrowsing}// namespace mozilla